/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.graph.test;

import java.util.function.Function;

import junit.framework.TestSuite;

import org.apache.jena.graph.BlankNodeId;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.graph.impl.LiteralLabel;
import org.apache.jena.graph.impl.LiteralLabelFactory;
import org.apache.jena.shared.PrefixMapping;

public class TestTriple extends GraphTestBase {

    public TestTriple(String name) {
        super(name);
    }

    public static TestSuite suite() {
        return new TestSuite(TestTriple.class);
    }

    private static final String U = "http://some.domain.name/magic/spells.incant";
    private static final String N = "Alice";
    private static final LiteralLabel L = LiteralLabelFactory.createLang("ashes are burning", "en");

    public void testTripleEquals() {
        // create some nodes to test
        String id = BlankNodeId.createFreshId();
        LiteralLabel L2 = LiteralLabelFactory.createLang(id.toString(), "");
        String U2 = id.toString();
        String N2 = id.toString();

        @SuppressWarnings("deprecation")
        Node[] nodes = new Node[]{Node.ANY, NodeFactory.createBlankNode(id), NodeFactory.createBlankNode(), NodeFactory.createLiteral(L),
            NodeFactory.createLiteral(L2), NodeFactory.createURI(U), NodeFactory.createURI(U2), NodeFactory.createVariable(N),
            NodeFactory.createVariable(N2)};

        Triple[] triples = new Triple[nodes.length * nodes.length * nodes.length];
        for ( int i = 0 ; i < nodes.length ; i++ ) {
            for ( int j = 0 ; j < nodes.length ; j++ ) {
                for ( int k = 0 ; k < nodes.length ; k++ ) {
                    triples[i * nodes.length * nodes.length + j * nodes.length + k] = Triple.create(nodes[i], nodes[j], nodes[k]);
                }
            }
        }

        // set up the expected results matrix
        // a expected[i][j] is true if triples[i] equals triples[j]
        // triples are expected to be equals if there components are equal
        boolean[][] expected = new boolean[triples.length][triples.length];
        for ( int i1 = 0 ; i1 < nodes.length ; i1++ ) {
            for ( int j1 = 0 ; j1 < nodes.length ; j1++ ) {
                for ( int k1 = 0 ; k1 < nodes.length ; k1++ ) {
                    for ( int i2 = 0 ; i2 < nodes.length ; i2++ ) {
                        for ( int j2 = 0 ; j2 < nodes.length ; j2++ ) {
                            for ( int k2 = 0 ; k2 < nodes.length ; k2++ ) {
                                expected[i1 * nodes.length * nodes.length + j1 * nodes.length + k1][i2 * nodes.length * nodes.length
                                                                                                    + j2 * nodes.length
                                                                                                    + k2] = nodes[i1].equals(nodes[i2])
                                                                                                            && nodes[j1].equals(nodes[j2])
                                                                                                            && nodes[k1].equals(nodes[k2]);
                            }
                        }
                    }
                }
            }
        }

        assertEquals("triple, null", triples[0].equals(null), false);
        assertDiffer("triple, string", triples[0], "string");

        // now compare each triple with each other triple
        for ( int i = 0 ; i < triples.length ; i++ ) {
            for ( int j = 0 ; j < triples.length ; j++ ) {
                if ( expected[i][j] ) {
                    assertEquals("triples " + i + ", " + j, triples[i], triples[j]);
                } else {
                    assertDiffer("triples" + i + ", " + j, triples[i], triples[j]);
                }
            }
        }
    }

    public void testTripleCreate() {
        Node S = NodeCreateUtils.create("s"), P = NodeCreateUtils.create("p"), O = NodeCreateUtils.create("o");
        assertEquals(Triple.create(S, P, O), Triple.create(S, P, O));
    }

    public void testTripleCreateFromString() {
        Node S = NodeCreateUtils.create("a"), P = NodeCreateUtils.create("_P"), O = NodeCreateUtils.create("?c");
        assertEquals(Triple.create(S, P, O), NodeCreateUtils.createTriple("a _P ?c"));
    }

    /**
     * Test that triple-creation respects prefixes, assuming that node creation does.
     */
    public void testTriplePrefixes() {
        Node S = NodeCreateUtils.create("rdf:alpha"), P = NodeCreateUtils.create("dc:creator");
        Node O = NodeCreateUtils.create("spoo:notmapped");
        Triple t = NodeCreateUtils.createTriple("rdf:alpha dc:creator spoo:notmapped");
        assertEquals(Triple.create(S, P, O), t);
    }

    public void testTripleCreationMapped() {
        PrefixMapping pm = PrefixMapping.Factory.create().setNsPrefix("a", "ftp://foo/").setNsPrefix("b", "http://spoo/");
        Triple wanted = NodeCreateUtils.createTriple("ftp://foo/x http://spoo/y c:z");
        Triple got = NodeCreateUtils.createTriple(pm, "a:x b:y c:z");
        assertEquals(wanted, got);
    }

    public void testPlainTripleMatches() {
        testMatches("S P O");
        testMatches("_S _P _O");
        testMatches("1 2 3");
    }

    public void testAnyTripleMatches() {
        testMatches("?? P O", "Z P O");
        testMatches("S ?? O", "S Q O");
        testMatches("S P ??", "S P oh");
        testMatches("?? ?? ??", "X Y Z");
        testMatches("?? ?? ??", "X Y 1");
        testMatches("?? ?? ??", "_X Y Z");
        testMatches("?? ?? ??", "X _Y Z");
    }

    private void testMatches(String triple) {
        testMatches(triple, triple);
    }

    private void testMatches(String pattern, String triple) {
        assertTrue(NodeCreateUtils.createTriple(pattern).matches(NodeCreateUtils.createTriple(triple)));
    }

    public void testPlainTripleDoesntMatch() {
        testMatchFails("S P O", "Z P O");
        testMatchFails("S P O", "S Q O");
        testMatchFails("S P O", "S P oh");
    }

    public void testAnyTripleDoesntMatch() {
        testMatchFails("?? P O", "S P oh");
        testMatchFails("S ?? O", "Z R O");
        testMatchFails("S P ??", "Z P oh");
    }

    public void testMatchFails(String pattern, String triple) {
        assertFalse(NodeCreateUtils.createTriple(pattern).matches(NodeCreateUtils.createTriple(triple)));
    }

    public void testMatchesNodes() {
        assertTrue(NodeCreateUtils.createTriple("S P O").matches(node("S"), node("P"), node("O")));
        assertTrue(NodeCreateUtils.createTriple("?? P O").matches(node("Z"), node("P"), node("O")));
        assertTrue(NodeCreateUtils.createTriple("S ?? O").matches(node("S"), node("Q"), node("O")));
        assertTrue(NodeCreateUtils.createTriple("S P ??").matches(node("S"), node("P"), node("I")));
        /* */
        assertFalse(NodeCreateUtils.createTriple("S P O").matches(node("Z"), node("P"), node("O")));
        assertFalse(NodeCreateUtils.createTriple("S P O").matches(node("S"), node("Q"), node("O")));
        assertFalse(NodeCreateUtils.createTriple("S P O").matches(node("Z"), node("P"), node("I")));
    }

    public void testElementMatches() {
        assertTrue(NodeCreateUtils.createTriple("S P O").subjectMatches(node("S")));
        assertTrue(NodeCreateUtils.createTriple("S P O").predicateMatches(node("P")));
        assertTrue(NodeCreateUtils.createTriple("S P O").objectMatches(node("O")));
        /* */
        assertFalse(NodeCreateUtils.createTriple("S P O").subjectMatches(node("Z")));
        assertFalse(NodeCreateUtils.createTriple("S P O").predicateMatches(node("Q")));
        assertFalse(NodeCreateUtils.createTriple("S P O").objectMatches(node("I")));
        /* */
        assertTrue(NodeCreateUtils.createTriple("?? P O").subjectMatches(node("SUB")));
        assertTrue(NodeCreateUtils.createTriple("S ?? O").predicateMatches(node("PRED")));
        assertTrue(NodeCreateUtils.createTriple("S P ??").objectMatches(node("OBJ")));
    }

    public void testConcrete() {
        assertTrue(NodeCreateUtils.createTriple("S P O").isConcrete());
        assertTrue(NodeCreateUtils.createTriple("S P 11").isConcrete());
        assertTrue(NodeCreateUtils.createTriple("S P _X").isConcrete());
        assertTrue(NodeCreateUtils.createTriple("S _P 11").isConcrete());
        assertTrue(NodeCreateUtils.createTriple("_S _P _O").isConcrete());
        assertTrue(NodeCreateUtils.createTriple("10 11 12").isConcrete());
        assertTrue(NodeCreateUtils.createTriple("S P 11").isConcrete());
        assertFalse(NodeCreateUtils.createTriple("?? P 11").isConcrete());
        assertFalse(NodeCreateUtils.createTriple("S ?? 11").isConcrete());
        assertFalse(NodeCreateUtils.createTriple("S P ??").isConcrete());
        assertFalse(NodeCreateUtils.createTriple("?S P 11").isConcrete());
        assertFalse(NodeCreateUtils.createTriple("S ?P 11").isConcrete());
        assertFalse(NodeCreateUtils.createTriple("S P ?O").isConcrete());
    }

    /**
     * Primarily to make sure that literals get quoted and stuff comes out in some
     * kind of coherent order.
     */
    public void testTripleToStringOrdering() {
        Triple t1 = NodeCreateUtils.createTriple("subject predicate object");
        assertTrue("subject must be present", t1.toString().contains("subject"));
        assertTrue("subject must preceed predicate", t1.toString().indexOf("subject") < t1.toString().indexOf("predicate"));
        assertTrue("predicate must preceed object", t1.toString().indexOf("predicate") < t1.toString().indexOf("object"));
    }

    public void testTripleToStringQuoting() {
        Triple t1 = NodeCreateUtils.createTriple("subject predicate 'object'");
        assertTrue(t1.toString().indexOf("object") > 0);
    }

    public void testTripleToStringWithPrefixing() {
        PrefixMapping pm = PrefixMapping.Factory.create();
        pm.setNsPrefix("spoo", "eg://domain.dom/spoo#");
        Triple t1 = NodeCreateUtils.createTriple("eg://domain.dom/spoo#a b c");
        // assertEquals( "spoo:a <eh:/b> <eh:/c>", t1.toString( pm ) );
        assertEquals("spoo:a eh:/b eh:/c", t1.toString(pm));
    }

    public void testTripleMaps() {
        assertEquals(node("x"), getSubject.apply(NodeCreateUtils.createTriple("x P z")));
        assertEquals(node("P"), getPredicate.apply(NodeCreateUtils.createTriple("x P z")));
        assertEquals(node("z"), getObject.apply(NodeCreateUtils.createTriple("x P z")));
    }

    public static final Function<Triple, Node> getSubject = t -> t.getSubject();
    public static final Function<Triple, Node> getPredicate = t -> t.getPredicate();
    public static final Function<Triple, Node> getObject = t -> t.getObject();
}
